// #![allow(unused)]
use std::collections::HashMap;
use std::ptr::null_mut;
use std::time::Instant;

use ab_glyph::FontArc;
use image::Rgba;
use minifb::Window;
use rand::{thread_rng, Rng};
use cgmath::Vector2;
use minifb::{Key, WindowOptions};

use crate::renderer::*;
use crate::background::*;
use crate::TEXTURES;
use crate::{HEIGHT, SCREEN_SIZE, WIDTH, WORK_DIRECTORY};
use crate::camera::*;
use crate::dinosaur::*;
use crate::obstruction::*;
use crate::util::{are_2rectangles_intersected, get_digits, keep_obstruction, world_to_screen, KeyboardState};


#[repr(C)]
pub struct GameApp {
    pub flag_render: bool,
    pub flag_with_window: bool,
    pub flag_extract_game_info: bool,

    pub target_update_fps: u64,
    pub last_update_instant: Instant,
    pub min_update_inteval: f64,
    pub max_update_inteval: f64,

    pub renderer: Option<Renderer>,
    pub target_fps: u64,
    pub last_render_instant: Instant,

    pub flag_running: bool,

    pub flag_fail: bool,
    pub flag_pause: bool,

    pub flag_draw_collision_boxes: bool,

    pub camera: Camera,

    pub dinosaur: Dinosaur,
    pub fireballs: Vec<Fireball>,

    pub pterosaurs: Vec<Pterosaur>,
    pub cactuses: Vec<Cactus>,
    pub meteorites: Vec<Meteorite>,
    pub time_since_last_obstruction_gen: f64,

    pub background: Background,

    pub health_textures: Vec<Texture>,
    pub energy_textures: Vec<Texture>,

    pub score: u64,
    pub score_timer: f64,
    pub ui_textures: HashMap<String, Texture>,

    pub font: Option<ab_glyph::FontArc>,

    pub keyboard_state: KeyboardState,
    pub p_released: bool,
    pub r_released: bool,

    pub info: *mut GameInfo,
    fireball_info_ptr: *mut Vec<FireballInfo>,
    pterosaur_info_ptr: *mut Vec<PterosaurInfo>,
    cactus_info_ptr: *mut Vec<CactusInfo>,
    meteorite_info_ptr: *mut Vec<MeteoriteInfo>,
}

impl GameApp {
    pub fn build(
        flag_render: bool,
        flag_with_window: bool,
        flag_extract_game_info: bool,
    ) -> Self {
        let target_update_fps = 60;
        let last_update_instant = Instant::now();
        let min_update_inteval = 1.0 / target_update_fps as f64;
        let max_update_inteval = min_update_inteval * 2.0;

        let target_fps = 60;
        let last_render_instant = last_update_instant;

        #[allow(unused_mut)]
        let mut window_options = WindowOptions::default();
        // window_options.borderless = true;
        // window_options.transparency = true;
        let renderer = if flag_render {
            Some(Renderer::new(
                "Mini Dino",
                WIDTH as _,
                HEIGHT as _,
                window_options,
                flag_with_window,
            ))
        } else {
            None
        };


        GameApp {
            flag_render,
            flag_with_window,
            flag_extract_game_info,

            target_update_fps,
            last_update_instant,
            min_update_inteval,
            max_update_inteval,

            renderer,
            target_fps,
            last_render_instant,

            flag_running: true,
            flag_fail: false,
            // dont forget to edit it
            flag_pause: if flag_render {
                true
            } else {
                false
            },
            flag_draw_collision_boxes: false,

            camera: Camera::new(
                Vector2::new(0.0, 100.0),
                Vector2::new(1000.0, 600.0),
            ),

            dinosaur: Dinosaur::new(),
            fireballs: Vec::new(),

            pterosaurs: Vec::new(),
            cactuses: Vec::new(),
            meteorites: Vec::new(),
            time_since_last_obstruction_gen: 0.8,

            background: Background::new(),

            health_textures: Vec::new(),
            energy_textures: Vec::new(),

            score: 0,
            score_timer: 1.0,
            ui_textures: HashMap::new(),

            font: None,

            keyboard_state: KeyboardState::new(),
            p_released: true,
            r_released: true,

            info: null_mut(),
            fireball_info_ptr: null_mut(),
            pterosaur_info_ptr: null_mut(),
            cactus_info_ptr: null_mut(),
            meteorite_info_ptr: null_mut(),
        }
    }

    pub fn get_window(&self) -> &Window {
        self.renderer.as_ref().unwrap().get_window()
    }

    #[no_mangle]
    pub extern "C" fn run(&mut self) {
        self.initialize();
        self.main_loop();
        self.clean_up();
    }

    #[no_mangle]
    pub extern "C" fn initialize(&mut self) {
        // this is the first call of it, so it would load files
        TEXTURES();
        for i in 1..=3 {
            self.health_textures.push(
                TEXTURES()[&format!("Heart{}", i)].clone()
            );
        }
        for i in 0..=3 {
            self.energy_textures.push(
                TEXTURES()[&format!("Energy{}", i)].clone()
            );
        }

        let font_data = std::fs::read(
            WORK_DIRECTORY().join("assets/font/TEXTS.ttf")
        ).unwrap();
        let font =
            FontArc::try_from_vec(font_data).unwrap();
        let words_to_load = vec![
            "PAUSED",
            "FAILED",
            "SCORE: ",
        ];
        for &word in words_to_load.iter() {
            self.ui_textures.insert(
                word.into(),
                Texture::create_text_texture(word, &font, 36.0, Rgba([255, 255, 255, 255])),
            );
        }
        self.font = Some(font);

        self.dinosaur.initialize();
        self.background.initialize();

        if self.flag_extract_game_info {
            self.fireball_info_ptr = Box::into_raw(Box::new(
                Vec::<FireballInfo>::new()
            ));
            self.pterosaur_info_ptr = Box::into_raw(Box::new(
                Vec::<PterosaurInfo>::new()
            ));
            self.cactus_info_ptr = Box::into_raw(Box::new(
                Vec::<CactusInfo>::new()
            ));
            self.meteorite_info_ptr = Box::into_raw(Box::new(
                Vec::<MeteoriteInfo>::new()
            ));
            self.sync_game_info();
        }
        println!("initialize complete, from rust");
    }

    fn sync_game_info(&mut self) {
        unsafe {
            if !self.info.is_null() {
                drop(Box::from_raw((*self.info).dinosaur as *mut DinoInfo));
            }
            (*self.fireball_info_ptr).clear();
            (*self.pterosaur_info_ptr).clear();
            (*self.cactus_info_ptr).clear();
            (*self.meteorite_info_ptr).clear();
        }

        let dinosaur = Box::into_raw(Box::new(DinoInfo {
            position: self.dinosaur.get_position(),
            velocity: self.dinosaur.get_velocity(),
            collision_box_lu:
                self.dinosaur.get_current_collision_box().0 + self.dinosaur.get_position(),
            collision_box_rd:
                self.dinosaur.get_current_collision_box().1 + self.dinosaur.get_position(),
            state: self.dinosaur.get_state() as i32,
            health: self.dinosaur.get_current_health(),
            energy: self.dinosaur.get_current_energy(),
        })) as *const _;

        for fb in self.fireballs.iter() {
            unsafe {
                (*self.fireball_info_ptr).push(FireballInfo {
                    position: fb.get_position(),
                    velocity: fb.get_velocity(),
                    collision_box_lu:
                        fb.get_current_collision_box().0 + fb.get_position(),
                    collision_box_rd:
                        fb.get_current_collision_box().1 + fb.get_position(),
                })
            }
        }
        let fireball_count =
            self.fireballs.len() as u64;

        for pt in self.pterosaurs.iter() {
            unsafe {
                (*self.pterosaur_info_ptr).push(PterosaurInfo {
                    position: pt.get_position(),
                    velocity: pt.get_velocity(),
                    collision_box_lu:
                        pt.get_current_collision_box().0 + pt.get_position(),
                    collision_box_rd:
                        pt.get_current_collision_box().1 + pt.get_position(),
                })
            }
            // println!("pt.position = {:?}", pt.get_position());
        }
        let pterosaur_count =
            self.pterosaurs.len() as u64;


        for cac in self.cactuses.iter() {
            unsafe {
                (*self.cactus_info_ptr).push(CactusInfo {
                    position: cac.get_position(),
                    velocity: cac.get_velocity(),
                    collision_box_lu:
                        cac.get_current_collision_box().0 + cac.get_position(),
                    collision_box_rd:
                        cac.get_current_collision_box().1 + cac.get_position(),
                })
            }
        }
        let cactus_count =
            self.cactuses.len() as u64;

        for met in self.meteorites.iter() {
            unsafe {
                (*self.meteorite_info_ptr).push(MeteoriteInfo {
                    position: met.get_position(),
                    velocity: met.get_velocity(),
                    collision_box_lu:
                        met.get_current_collision_box().0 + met.get_position(),
                    collision_box_rd:
                        met.get_current_collision_box().1 + met.get_position(),
                })
            }
        }
        let meteorite_count =
            self.meteorites.len() as u64;

        unsafe {
            self.info = Box::into_raw(Box::new(
                GameInfo {
                    dinosaur,
                    fireballs: (*self.fireball_info_ptr).as_ptr(),
                    fireball_count,

                    pterosaurs: (*self.pterosaur_info_ptr).as_ptr(),
                    pterosaur_count,
                    cactuses: (*self.cactus_info_ptr).as_ptr(),
                    cactus_count,
                    meteorites: (*self.meteorite_info_ptr).as_ptr(),
                    meteorite_count,

                    flag_fail: self.flag_fail,
                    flag_pause: self.flag_pause,

                    score: self.score,
                }
            ));
        }
    }

    fn main_loop(&mut self) {
        while self.flag_running {
            self.handle_input();
            self.update();
            self.render();
        }
    }

    #[no_mangle]
    pub extern "C" fn clean_up(&mut self) {
        unsafe {
            if !self.info.is_null() {
                drop(Box::from_raw((*self.info).dinosaur as *mut DinoInfo));
                drop(Box::from_raw(self.fireball_info_ptr));
                drop(Box::from_raw(self.pterosaur_info_ptr));
                drop(Box::from_raw(self.cactus_info_ptr));
                drop(Box::from_raw(self.meteorite_info_ptr));
                self.info = null_mut();
                self.fireball_info_ptr = null_mut();
                self.pterosaur_info_ptr = null_mut();
                self.cactus_info_ptr = null_mut();
                self.meteorite_info_ptr = null_mut();
            }
        }
    }

    #[no_mangle]
    pub extern "C" fn reset(&mut self) {
        self.dinosaur = Dinosaur::new();
        self.dinosaur.initialize();
        self.fireballs.clear();

        self.cactuses.clear();
        self.pterosaurs.clear();
        self.meteorites.clear();

        self.background = Background::new();
        self.background.initialize();

        self.score = 0;
        self.score_timer = 1.0;
        
        self.flag_pause = if self.flag_render {
            true
        } else {
            false
        };
        self.flag_fail = false;

        if self.flag_extract_game_info {
            self.sync_game_info();
        }
    }

    fn handle_input(&mut self) {
        self.keyboard_state.query(
            self.renderer.as_ref().unwrap().get_window()
        );

        if self.keyboard_state.is_key_pressed(Key::Escape) {
            self.flag_running = false;
        }
        if self.keyboard_state.is_key_pressed(Key::P) {
            if self.p_released {
                self.flag_pause = !self.flag_pause;
                self.p_released = false;
            }
        } else {
            self.p_released = true;
        }
        if self.keyboard_state.is_key_pressed(Key::R) {
            if self.r_released {
                self.reset();
                self.r_released = false;
            }
        } else {
            self.r_released = true;
        }

        if !self.flag_fail && !self.flag_pause {
            self.dinosaur.handle_input(&self.keyboard_state);
        }
    }

    fn update(&mut self) {
        // stop update if fail or pause
        if self.flag_fail || self.flag_pause {
            return;
        }

        let current_instant = Instant::now();
        let mut delta_time = (current_instant - self.last_update_instant).as_secs_f64();

        if delta_time < self.min_update_inteval {
            return;
        }

        if delta_time > self.max_update_inteval {
            delta_time = self.max_update_inteval;
        }

        self.last_update_instant = current_instant;

        // check if dino dead
        if self.dinosaur.get_current_health() == 0 {
            // game over
            self.flag_fail = true;
            self.dinosaur.fail();
            return;
        }

        // scoring
        self.score_timer -= delta_time;
        if self.score_timer <= 0.0 {
            self.score += 10;
            self.score_timer = 1.0;
        }

        // gen obstructions
        self.time_since_last_obstruction_gen += delta_time;
        if self.time_since_last_obstruction_gen >= 0.8 {
            self.generate_obstruction();
        }

        self.background.update(delta_time);
        for fireball in self.fireballs.iter_mut() {
            fireball.update(delta_time);
        }

        for i_pterosaur in self.pterosaurs.iter_mut() {
            i_pterosaur.update(delta_time);
        }
        for i_cactus in self.cactuses.iter_mut() {
            i_cactus.update(delta_time);
        }
        for i_meteorite in self.meteorites.iter_mut() {
            i_meteorite.update(delta_time);
        }

        match self.dinosaur.update(delta_time) {
            Some(fireball_count) => {
                for _ in 0..fireball_count {
                    let current_energy = self.dinosaur.get_current_energy();
                    if current_energy < 2 {
                        break;
                    }

                    self.dinosaur.set_current_energy(current_energy - 2);
                    self.fireballs.push(
                        Fireball::gen(
                            self.dinosaur.get_position(),
                            Vector2::new(600.0, 0.0),
                        )
                    );
                }
            }
            None => {}
        }

        let mut pterosaurs_to_remove = vec![false; self.pterosaurs.len()];
        let mut cactuses_to_remove = vec![false; self.cactuses.len()];
        let mut meteorites_to_remove = vec![false; self.meteorites.len()];
        let mut fireballs_to_remove = vec![false; self.fireballs.len()];

        // collision detection
        let dino_invincible = self.dinosaur.is_invincible();
        let (dino_lu, dino_rd) = (
            self.dinosaur.get_current_collision_box().0 + self.dinosaur.get_position(),
            self.dinosaur.get_current_collision_box().1 + self.dinosaur.get_position(),
        );
        for (idx, i_pterosaur) in self.pterosaurs.iter().enumerate() {
            let (ref obs_lu, ref obs_rd) =
                i_pterosaur.get_current_collision_box();
            let (obs_lu, obs_rd) = (
                obs_lu + i_pterosaur.get_position(),
                obs_rd + i_pterosaur.get_position(),
            );
            for i_fireball in self.fireballs.iter() {
                let (fb_lu, fb_rd) = i_fireball.get_current_collision_box();
                let (fb_lu, fb_rd) = (
                    fb_lu + i_fireball.get_position(),
                    fb_rd + i_fireball.get_position(),
                );
                if are_2rectangles_intersected(
                    &fb_lu,
                    &fb_rd,
                    &obs_lu,
                    &obs_rd
                ) {
                    pterosaurs_to_remove[idx] = true;
                    self.score += 20;
                }
            }
            if are_2rectangles_intersected(
                &dino_lu,
                &dino_rd,
                &obs_lu,
                &obs_rd
            ) && !dino_invincible {
                // println!("ptero: {}, {}; {}, {}", obs_lu.x, obs_lu.y, obs_rd.x, obs_rd.y);
                // println!("dino: {}, {}; {}, {}", dino_lu.x, dino_lu.y, obs_rd.x, obs_rd.y);
                self.dinosaur.hurt();
                self.dinosaur.set_invincible(2.0);
            }
        }
        for (idx, i_cactus) in self.cactuses.iter().enumerate() {
            let (obs_lu, obs_rd) =
                i_cactus.get_current_collision_box();
            let (obs_lu, obs_rd) = (
                obs_lu + i_cactus.get_position(),
                obs_rd + i_cactus.get_position(),
            );
            for i_fireball in self.fireballs.iter() {
                let (fb_lu, fb_rd) = i_fireball.get_current_collision_box();
                let (fb_lu, fb_rd) = (
                    fb_lu + i_fireball.get_position(),
                    fb_rd + i_fireball.get_position(),
                );
                // println!("fireball: {}, {}; {}, {}", fb_lu.x, fb_lu.y, fb_rd.x, fb_rd.y);
                if are_2rectangles_intersected(
                    &fb_lu,
                    &fb_rd,
                    &obs_lu,
                    &obs_rd
                ) {
                    cactuses_to_remove[idx] = true;
                    self.score += 10;
                }
            }
            if are_2rectangles_intersected(
                &dino_lu,
                &dino_rd,
                &obs_lu,
                &obs_rd
            ) && !dino_invincible {
                // println!("cactus: {}, {}; {}, {}", obs_lu.x, obs_lu.y, obs_rd.x, obs_rd.y);
                // println!("dino: {}, {}; {}, {}", dino_lu.x, dino_lu.y, obs_rd.x, obs_rd.y);
                self.dinosaur.hurt();
                self.dinosaur.set_invincible(2.0);
            }
        }
        for (idx, i_meteorite) in self.meteorites.iter().enumerate() {
            let (ref obs_lu, ref obs_rd) =
                i_meteorite.get_current_collision_box();
            let (obs_lu, obs_rd) = (
                obs_lu + i_meteorite.get_position(),
                obs_rd + i_meteorite.get_position(),
            );
            for i_fireball in self.fireballs.iter() {
                let (fb_lu, fb_rd) = i_fireball.get_current_collision_box();
                let (fb_lu, fb_rd) = (
                    fb_lu + i_fireball.get_position(),
                    fb_rd + i_fireball.get_position(),
                );
                if are_2rectangles_intersected(
                    &fb_lu,
                    &fb_rd,
                    &obs_lu,
                    &obs_rd
                ) {
                    meteorites_to_remove[idx] = true;
                    self.score += 30;
                }
            }
            if are_2rectangles_intersected(
                &dino_lu,
                &dino_rd,
                &obs_lu,
                &obs_rd
            ) && !dino_invincible {
                // println!("meteo: {}, {}; {}, {}", obs_lu.x, obs_lu.y, obs_rd.x, obs_rd.y);
                // println!("dino: {}, {}; {}, {}", dino_lu.x, dino_lu.y, obs_rd.x, obs_rd.y);
                self.dinosaur.hurt();
                self.dinosaur.set_invincible(2.0);
            }
        }

        // clear things out of screen
        for i in 0..self.fireballs.len() {
            if !keep_obstruction(
                &self.fireballs[i].get_position(),
            ) {
                fireballs_to_remove[i] = true;
            }
        }
        for i in 0..self.pterosaurs.len() {
            if !keep_obstruction(
                &self.pterosaurs[i].get_position(),
            ) {
                pterosaurs_to_remove[i] = true;
                self.score += 20;
            }
        }

        for i in 0..self.cactuses.len() {
            if !keep_obstruction(
                &self.cactuses[i].get_position(),
            ) {
                cactuses_to_remove[i] = true;
                self.score += 10;
            }
        }
        for i in 0..self.meteorites.len() {
            if !keep_obstruction(
                &self.meteorites[i].get_position(),
            ) {
                meteorites_to_remove[i] = true;
                self.score += 30;
            }
        }


        let mut pterosaur_remove_iter = pterosaurs_to_remove.iter();
        self.pterosaurs.retain(|_| {
            !*pterosaur_remove_iter.next().unwrap()
        });
        let mut cactus_remove_iter = cactuses_to_remove.iter();
        self.cactuses.retain(|_| {
            !*cactus_remove_iter.next().unwrap()
        });
        let mut meteorite_remove_iter = meteorites_to_remove.iter();
        self.meteorites.retain(|_| {
            !*meteorite_remove_iter.next().unwrap()
        });
        let mut fireball_remove_iter = fireballs_to_remove.iter();
        self.fireballs.retain(|_| {
            !*fireball_remove_iter.next().unwrap()
        });

        // println!("update complete");
    }

    fn render(&mut self) {
        let current_instant = Instant::now();
        let inteval = (current_instant - self.last_render_instant).as_secs_f64();
        if inteval < self.min_update_inteval {
            return;
        }

        self.renderer.as_mut().unwrap().clear(
            Rgba([30, 30, 30, 255])
        );

        self.background.draw(
            self.renderer.as_mut().unwrap(),
            &self.camera,
        );
        for i_pterosaur in self.pterosaurs.iter() {
            i_pterosaur.draw(
                self.renderer.as_mut().unwrap(),
                &self.camera,
            );
        }
        for i_cactus in self.cactuses.iter() {
            i_cactus.draw(
                self.renderer.as_mut().unwrap(),
                &self.camera,
            );
        }
        for i_meteorite in self.meteorites.iter() {
            i_meteorite.draw(
                self.renderer.as_mut().unwrap(),
                &self.camera,
            );
        }
        self.dinosaur.draw(
            self.renderer.as_mut().unwrap(),
            &self.camera,
        );
        for fireball in self.fireballs.iter() {
            fireball.draw(
                self.renderer.as_mut().unwrap(),
                &self.camera
            );
        }


        if self.flag_draw_collision_boxes {
            self.dinosaur.draw_collision_box(
                self.renderer.as_mut().unwrap(),
                &self.camera
            );
            for i_fireball in self.fireballs.iter() {
                i_fireball.draw_collision_box(
                    self.renderer.as_mut().unwrap(),
                    &self.camera
                );
            }
            for i_pterosaur in self.pterosaurs.iter() {
                i_pterosaur.draw_collision_box(
                    self.renderer.as_mut().unwrap(),
                    &self.camera
                );
            }
            for i_cactus in self.cactuses.iter() {
                i_cactus.draw_collision_box(
                    self.renderer.as_mut().unwrap(),
                    &self.camera,
                );
            }
            for i_meteorite in self.meteorites.iter() {
                i_meteorite.draw_collision_box(
                    self.renderer.as_mut().unwrap(),
                    &self.camera,
                );
            }
        }

        self.draw_ui();

        if self.flag_with_window {
            self.renderer.as_mut().unwrap().present();
        }
    }

    fn draw_ui(
        &mut self,
    ) {
        let screen_size = Vector2::new(WIDTH as f64, HEIGHT as f64);
        // health and energy
        let health_leftup_world = Vector2::new(-450.0, -100.0);
        let health_rightdown_world = Vector2::new(-250.0, -160.0);
        let energy_leftup_world = Vector2::new(-200.0, -100.0);
        let energy_rightdown_world = Vector2::new(-000.0, -160.0);

        let health_leftup_screen = world_to_screen(
            &health_leftup_world,
            &self.camera,
            &screen_size,
        );
        let health_rightdown_screen = world_to_screen(
            &health_rightdown_world,
            &self.camera,
            &screen_size,
        );
        let energy_leftup_screen = world_to_screen(
            &energy_leftup_world,
            &self.camera,
            &screen_size,
        );
        let energy_rightdown_screen = world_to_screen(
            &energy_rightdown_world,
            &self.camera,
            &screen_size,
        );

        let idx_health_texture = (self.dinosaur.get_current_health() - 1).max(0);
        let idx_energy_texture = self.dinosaur.get_current_energy();

        self.renderer.as_mut().unwrap().draw_texture(
            &health_leftup_screen,
            &health_rightdown_screen,
            &self.health_textures[idx_health_texture as usize],
        );

        self.renderer.as_mut().unwrap().draw_texture(
            &energy_leftup_screen,
            &energy_rightdown_screen,
            &self.energy_textures[idx_energy_texture as usize],
        );

        // score
        let score_leftup_screen = world_to_screen(
            &Vector2::new(-480.0, 400.0),
            &self.camera,
            &screen_size,
        );
        let score_rightdown_screen = world_to_screen(
            &Vector2::new(-300.0, 360.0),
            &self.camera,
            &screen_size,
        );

        self.renderer.as_mut().unwrap().draw_texture(
            &score_leftup_screen,
            &score_rightdown_screen,
            &self.ui_textures["SCORE: "],
        );


        let score_digit = get_digits(self.score) as f64;
        let score_points_texture =
            Texture::create_text_texture(
                &format!("{}", self.score),
                self.font.as_ref().unwrap(),
                36.0,
                Rgba([255, 255, 255, 255]),
            );
        let score_points_leftup_screen = world_to_screen(
            &Vector2::new(-280.0, 400.0),
            &self.camera,
            &screen_size,
        );
        let score_points_rightdown_screen = world_to_screen(
            &Vector2::new(-280.0 + 36.0 * score_digit, 360.0),
            &self.camera,
            &screen_size,
        );

        self.renderer.as_mut().unwrap().draw_texture(
            &score_points_leftup_screen,
            &score_points_rightdown_screen,
            &score_points_texture,
        );


        if self.flag_fail {
            let lu_w = Vector2::new( 242.0, -124.0);
            let rd_w = Vector2::new( 458.0, -196.0);
            let lu_s = world_to_screen(&lu_w, &self.camera, &SCREEN_SIZE);
            let rd_s = world_to_screen(&rd_w, &self.camera, &SCREEN_SIZE);
            self.renderer.as_mut().unwrap().draw_texture(
                &lu_s,
                &rd_s,
                &self.ui_textures["FAILED"],
            );
        } else if self.flag_pause {
            let lu_w = Vector2::new( 242.0, -124.0);
            let rd_w = Vector2::new( 458.0, -196.0);
            let lu_s = world_to_screen(&lu_w, &self.camera, &SCREEN_SIZE);
            let rd_s = world_to_screen(&rd_w, &self.camera, &SCREEN_SIZE);
            self.renderer.as_mut().unwrap().draw_texture(
                &lu_s,
                &rd_s,
                &self.ui_textures["PAUSED"],
            );
        }
    }

    fn generate_obstruction(&mut self) {
        let mut danger_points = 0.1;
        danger_points += 1.0 * self.cactuses.len() as f64;
        danger_points += 1.2 * self.pterosaurs.len() as f64;
        danger_points += 1.5 * self.meteorites.len() as f64;

        // modify the number before danger_points, the larger the easier
        let probability = 0.1 + (self.time_since_last_obstruction_gen / (f64::exp(0.25 * danger_points) + 1.0));
        // println!("prob{}", probability);
        let rand_num = thread_rng().gen_range(0.0..=1.0);
        // println!("rand_num{}", rand_num);

        if rand_num > probability {
            return;
        }
        // println!("gened\n");

        let obstruction_type = thread_rng().gen_range(0..=2);
        if obstruction_type == 0{
            self.pterosaurs.push(
                Pterosaur::gen(
                    Vector2::new(600.0, thread_rng().gen_range(60.0..=200.0)),
                    Vector2::new(thread_rng().gen_range(-800.0..=-200.0), 0.0),
                )
            );
            self.time_since_last_obstruction_gen = 0.0;
        } else if obstruction_type == 1 {
            self.cactuses.push(
                Cactus::gen(
                    thread_rng().gen_range(0..=5),
                    Vector2::new(600.0, 0.0),
                    self.background.get_ground_velocity(),
                )
            );
            self.time_since_last_obstruction_gen = 0.0;
        } else if obstruction_type == 2 {
            self.meteorites.push(
                Meteorite::gen(
                    Vector2::new(thread_rng().gen_range(-500.0..=500.0), 350.0),
                    Vector2::new(
                        thread_rng().gen_range(-600.0..=600.0),
                        -thread_rng().gen_range(300.0..=600.0),
                    ),
                )
            );
            self.time_since_last_obstruction_gen = 0.0;
        }
    }
}

#[repr(C)]
pub struct DinoInfo {
    pub position: Vector2<f64>,
    pub velocity: Vector2<f64>,
    pub collision_box_lu: Vector2<f64>,
    pub collision_box_rd: Vector2<f64>,
    pub state: i32,
    pub health: i64,
    pub energy: i64,
}

#[repr(C)]
pub struct FireballInfo {
    pub position: Vector2<f64>,
    pub velocity: Vector2<f64>,
    pub collision_box_lu: Vector2<f64>,
    pub collision_box_rd: Vector2<f64>,
}

#[derive(Debug)]
#[repr(C)]
pub struct PterosaurInfo {
    pub position: Vector2<f64>,
    pub velocity: Vector2<f64>,
    pub collision_box_lu: Vector2<f64>,
    pub collision_box_rd: Vector2<f64>,
}

#[derive(Debug)]
#[repr(C)]
pub struct CactusInfo {
    pub position: Vector2<f64>,
    pub velocity: Vector2<f64>,
    pub collision_box_lu: Vector2<f64>,
    pub collision_box_rd: Vector2<f64>,
}

#[derive(Debug)]
#[repr(C)]
pub struct MeteoriteInfo {
    pub position: Vector2<f64>,
    pub velocity: Vector2<f64>,
    pub collision_box_lu: Vector2<f64>,
    pub collision_box_rd: Vector2<f64>,
}

#[derive(Debug)]
#[repr(C)]
pub struct GameInfo {
    pub dinosaur: *const DinoInfo,

    pub fireballs: *const FireballInfo,
    pub fireball_count: u64,

    pub pterosaurs: *const PterosaurInfo,
    pub pterosaur_count: u64,

    pub cactuses: *const CactusInfo,
    pub cactus_count: u64,

    pub meteorites: *const MeteoriteInfo,
    pub meteorite_count: u64,

    pub flag_fail: bool,
    pub flag_pause: bool,

    pub score: u64,
}

impl GameApp {
    #[no_mangle]
    pub extern "C" fn get_game_info_ptr(
        &self,
    ) -> *const GameInfo {
        self.info
    }

    #[no_mangle]
    pub extern "C" fn step(
        &mut self,
        i_keyboard_state: *mut u8,
        delta_time: f64,
    ) -> bool {
        self.sync_game_info();
        let i_keyboard_state = unsafe {
            KeyboardState::from_raw_ptr(i_keyboard_state)
        };
        // println!("create kbs ok");
        self.handle_input_step(i_keyboard_state);
        // println!("input handle ok");
        self.update_step(delta_time);
        // println!("update ok");
        if self.flag_render {
            self.render_step();
        }
        // println!("render ok");

        self.flag_fail
    }

    fn handle_input_step(
        &mut self,
        i_keyboard_state: KeyboardState
    ) {
        // poll events
        if i_keyboard_state.is_key_pressed(Key::Escape) {
            self.flag_running = false;
        }
        if i_keyboard_state.is_key_pressed(Key::P) {
            if self.p_released {
                self.flag_pause = !self.flag_pause;
                self.p_released = false;
            }
        } else {
            self.p_released = true;
        }
        if i_keyboard_state.is_key_pressed(Key::R) {
            if self.r_released {
                self.reset();
                self.r_released = false;
            }
        } else {
            self.r_released = true;
        }

        if !self.flag_fail || !self.flag_pause {
            self.dinosaur.handle_input(&i_keyboard_state);
        }
    }

    fn update_step(
        &mut self,
        mut delta_time: f64,
    ) {
        // stop update if fail or pause
        if self.flag_fail || self.flag_pause {
            return;
        }

        if delta_time < self.min_update_inteval {
            delta_time = self.min_update_inteval;
            println!("delta time too short, already refined");
        }

        if delta_time > self.max_update_inteval {
            delta_time = self.max_update_inteval;
            println!("delta time too long, already refined");
        }

        // check if dead
        if self.dinosaur.get_current_health() == 0 {
            // game over
            self.flag_fail = true;
            self.dinosaur.fail();
            return;
        }

        // scoring
        self.score_timer -= delta_time;
        if self.score_timer <= 0.0 {
            self.score += 10;
            self.score_timer = 1.0;
        }

        // gen obstructions
        self.time_since_last_obstruction_gen += delta_time;
        if self.time_since_last_obstruction_gen >= 0.8 {
            self.generate_obstruction();
        }

        self.background.update(delta_time);
        for fireball in self.fireballs.iter_mut() {
            fireball.update(delta_time);
        }

        for i_pterosaur in self.pterosaurs.iter_mut() {
            i_pterosaur.update(delta_time);
        }
        for i_cactus in self.cactuses.iter_mut() {
            i_cactus.update(delta_time);
        }
        for i_meteorite in self.meteorites.iter_mut() {
            i_meteorite.update(delta_time);
        }

        match self.dinosaur.update(delta_time) {
            Some(fireball_count) => {
                for _ in 0..fireball_count {
                    let current_energy = self.dinosaur.get_current_energy();
                    if current_energy < 2 {
                        break;
                    }

                    self.dinosaur.set_current_energy(current_energy - 2);
                    self.fireballs.push(
                        Fireball::gen(
                            self.dinosaur.get_position(),
                            Vector2::new(600.0, 0.0),
                        )
                    );
                }
            }
            None => {}
        }

        let mut pterosaurs_to_remove = vec![false; self.pterosaurs.len()];
        let mut cactuses_to_remove = vec![false; self.cactuses.len()];
        let mut meteorites_to_remove = vec![false; self.meteorites.len()];
        let mut fireballs_to_remove = vec![false; self.fireballs.len()];

        // collision detection
        let dino_invincible = self.dinosaur.is_invincible();
        let (dino_lu, dino_rd) = (
            self.dinosaur.get_current_collision_box().0 + self.dinosaur.get_position(),
            self.dinosaur.get_current_collision_box().1 + self.dinosaur.get_position(),
        );
        for (idx, i_pterosaur) in self.pterosaurs.iter().enumerate() {
            let (ref obs_lu, ref obs_rd) =
                i_pterosaur.get_current_collision_box();
            let (obs_lu, obs_rd) = (
                obs_lu + i_pterosaur.get_position(),
                obs_rd + i_pterosaur.get_position(),
            );
            for i_fireball in self.fireballs.iter() {
                let (fb_lu, fb_rd) = i_fireball.get_current_collision_box();
                let (fb_lu, fb_rd) = (
                    fb_lu + i_fireball.get_position(),
                    fb_rd + i_fireball.get_position(),
                );
                if are_2rectangles_intersected(
                    &fb_lu,
                    &fb_rd,
                    &obs_lu,
                    &obs_rd
                ) {
                    pterosaurs_to_remove[idx] = true;
                    self.score += 20;
                }
            }
            if are_2rectangles_intersected(
                &dino_lu,
                &dino_rd,
                &obs_lu,
                &obs_rd
            ) && !dino_invincible {
                // println!("ptero: {}, {}; {}, {}", obs_lu.x, obs_lu.y, obs_rd.x, obs_rd.y);
                // println!("dino: {}, {}; {}, {}", dino_lu.x, dino_lu.y, obs_rd.x, obs_rd.y);
                self.dinosaur.hurt();
                self.dinosaur.set_invincible(2.0);
            }
        }
        for (idx, i_cactus) in self.cactuses.iter().enumerate() {
            let (obs_lu, obs_rd) =
                i_cactus.get_current_collision_box();
            let (obs_lu, obs_rd) = (
                obs_lu + i_cactus.get_position(),
                obs_rd + i_cactus.get_position(),
            );
            for i_fireball in self.fireballs.iter() {
                let (fb_lu, fb_rd) = i_fireball.get_current_collision_box();
                let (fb_lu, fb_rd) = (
                    fb_lu + i_fireball.get_position(),
                    fb_rd + i_fireball.get_position(),
                );
                // println!("fireball: {}, {}; {}, {}", fb_lu.x, fb_lu.y, fb_rd.x, fb_rd.y);
                if are_2rectangles_intersected(
                    &fb_lu,
                    &fb_rd,
                    &obs_lu,
                    &obs_rd
                ) {
                    cactuses_to_remove[idx] = true;
                    self.score += 10;
                }
            }
            if are_2rectangles_intersected(
                &dino_lu,
                &dino_rd,
                &obs_lu,
                &obs_rd
            ) && !dino_invincible {
                // println!("cactus: {}, {}; {}, {}", obs_lu.x, obs_lu.y, obs_rd.x, obs_rd.y);
                // println!("dino: {}, {}; {}, {}", dino_lu.x, dino_lu.y, obs_rd.x, obs_rd.y);
                self.dinosaur.hurt();
                self.dinosaur.set_invincible(2.0);
            }
        }
        for (idx, i_meteorite) in self.meteorites.iter().enumerate() {
            let (ref obs_lu, ref obs_rd) =
                i_meteorite.get_current_collision_box();
            let (obs_lu, obs_rd) = (
                obs_lu + i_meteorite.get_position(),
                obs_rd + i_meteorite.get_position(),
            );
            for i_fireball in self.fireballs.iter() {
                let (fb_lu, fb_rd) = i_fireball.get_current_collision_box();
                let (fb_lu, fb_rd) = (
                    fb_lu + i_fireball.get_position(),
                    fb_rd + i_fireball.get_position(),
                );
                if are_2rectangles_intersected(
                    &fb_lu,
                    &fb_rd,
                    &obs_lu,
                    &obs_rd
                ) {
                    meteorites_to_remove[idx] = true;
                    self.score += 30;
                }
            }
            if are_2rectangles_intersected(
                &dino_lu,
                &dino_rd,
                &obs_lu,
                &obs_rd
            ) && !dino_invincible {
                // println!("meteo: {}, {}; {}, {}", obs_lu.x, obs_lu.y, obs_rd.x, obs_rd.y);
                // println!("dino: {}, {}; {}, {}", dino_lu.x, dino_lu.y, obs_rd.x, obs_rd.y);
                self.dinosaur.hurt();
                self.dinosaur.set_invincible(2.0);
            }
        }

        // clear things out of screen
        for i in 0..self.fireballs.len() {
            if !keep_obstruction(
                &self.fireballs[i].get_position(),
            ) {
                fireballs_to_remove[i] = true;
            }
        }
        for i in 0..self.pterosaurs.len() {
            if !keep_obstruction(
                &self.pterosaurs[i].get_position(),
            ) {
                pterosaurs_to_remove[i] = true;
                self.score += 20;
            }
        }

        for i in 0..self.cactuses.len() {
            if !keep_obstruction(
                &self.cactuses[i].get_position(),
            ) {
                cactuses_to_remove[i] = true;
                self.score += 10;
            }
        }
        for i in 0..self.meteorites.len() {
            if !keep_obstruction(
                &self.meteorites[i].get_position(),
            ) {
                meteorites_to_remove[i] = true;
                self.score += 30;
            }
        }


        let mut pterosaur_remove_iter = pterosaurs_to_remove.iter();
        self.pterosaurs.retain(|_| {
            !*pterosaur_remove_iter.next().unwrap()
        });
        let mut cactus_remove_iter = cactuses_to_remove.iter();
        self.cactuses.retain(|_| {
            !*cactus_remove_iter.next().unwrap()
        });
        let mut meteorite_remove_iter = meteorites_to_remove.iter();
        self.meteorites.retain(|_| {
            !*meteorite_remove_iter.next().unwrap()
        });
        let mut fireball_remove_iter = fireballs_to_remove.iter();
        self.fireballs.retain(|_| {
            !*fireball_remove_iter.next().unwrap()
        });
    }

    fn render_step(&mut self) {
        let current_instant = Instant::now();
        let inteval = (current_instant - self.last_render_instant).as_secs_f64();
        if inteval < self.min_update_inteval {
            return;
        }

        self.renderer.as_mut().unwrap().clear(
            Rgba([50, 50, 50, 255])
        );

        self.background.draw(
            self.renderer.as_mut().unwrap(),
            &self.camera,
        );
        for i_pterosaur in self.pterosaurs.iter() {
            i_pterosaur.draw(
                self.renderer.as_mut().unwrap(),
                &self.camera,
            );
        }
        for i_cactus in self.cactuses.iter() {
            i_cactus.draw(
                self.renderer.as_mut().unwrap(),
                &self.camera,
            );
        }
        for i_meteorite in self.meteorites.iter() {
            i_meteorite.draw(
                self.renderer.as_mut().unwrap(),
                &self.camera,
            );
        }
        self.dinosaur.draw(
            self.renderer.as_mut().unwrap(),
            &self.camera,
        );
        for fireball in self.fireballs.iter() {
            fireball.draw(
                self.renderer.as_mut().unwrap(),
                &self.camera
            );
        }

        self.draw_ui();

        if self.flag_draw_collision_boxes {
            self.dinosaur.draw_collision_box(self.renderer.as_mut().unwrap(), &self.camera);
            for i_fireball in self.fireballs.iter() {
                i_fireball.draw_collision_box(self.renderer.as_mut().unwrap(), &self.camera);
            }
            for i_pterosaur in self.pterosaurs.iter() {
                i_pterosaur.draw_collision_box(self.renderer.as_mut().unwrap(), &self.camera);
            }
            for i_cactus in self.cactuses.iter() {
                i_cactus.draw_collision_box(self.renderer.as_mut().unwrap(), &self.camera);
            }
            for i_meteorite in self.meteorites.iter() {
                i_meteorite.draw_collision_box(self.renderer.as_mut().unwrap(), &self.camera);
            }
        }

        if self.flag_with_window {
            self.renderer.as_mut().unwrap().present();
        }
    }

    #[no_mangle]
    pub extern "C" fn get_buffer_ptr(
        &mut self
    ) -> *mut u8 {
        if !self.flag_render {
            panic!("flag_render is not set to true, thus no buffer to get");
        }

        self.renderer
            .as_mut().unwrap()
            .get_buffer_ptr()
    }
}